package demo2;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Scanner;

/**
 * 客户端类：发送3个字符对应三次场景，处理服务器响应并实现重传
 * 核心功能：生成带CRC校验的数据帧，根据服务器响应（ack/nak）或超时触发重传
 */
public class Client {
    private static final String SERVER_ADDRESS = "127.0.0.1";
    private static final int SERVER_PORT = 9000;
    private static final int TIMEOUT = 3000;
    private static final int FRAME_COUNT = 3;

    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket(SERVER_ADDRESS, SERVER_PORT)) {
            socket.setSoTimeout(TIMEOUT);
            System.out.println("客户端：已连接服务器");

            String data = getInputData();

            try (DataOutputStream out = new DataOutputStream(socket.getOutputStream());
                 DataInputStream in = new DataInputStream(socket.getInputStream())) {

                // 发送开始信号"$"并等待确认（确保服务器准备好接收）
                if (!sendStartSignal(out, in)) {
                    System.out.println("客户端：开始信号失败");
                    return;
                }

                if (!sendDataFrames(out, in, data)) {
                    System.out.println("客户端：数据传输失败");
                    return;
                }

                if (!sendEndSignal(out, in)) {
                    System.out.println("客户端：结束信号失败");
                    return;
                }

                System.out.println("客户端：数据传输完成！");
            }
        }
    }

    /**
     * 获取用户输入
     * 作用：保证发送的帧数量与服务器三次场景匹配
     */
    private static String getInputData() {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.next().trim();
        while (input.length() != FRAME_COUNT) {
            input = scanner.next().trim();
        }
        scanner.close();
        return "$" + input + "$"; // 包装开始/结束信号（便于服务器识别）
    }

    // 发送开始信号（复用通用信号发送方法）
    private static boolean sendStartSignal(DataOutputStream out, DataInputStream in) throws IOException {
        return sendSignal(out, in, "客户端：发送开始信号：$");
    }

    // 发送结束信号（复用通用信号发送方法）
    private static boolean sendEndSignal(DataOutputStream out, DataInputStream in) throws IOException {
        return sendSignal(out, in, "客户端：发送结束信号：$");
    }

    /**
     * 通用信号发送方法（带超时重传）
     * 作用：发送"$"信号并等待服务器确认，超时则重传
     */
    private static boolean sendSignal(DataOutputStream out, DataInputStream in, String sendLog) throws IOException {
        System.out.println(sendLog);
        out.writeUTF("$"); // 发送信号
        try {
            String resp = in.readUTF(); // 等待服务器确认
            if (resp.equals("$")) { // 收到服务器回复的确认信号
                System.out.println("客户端：收到服务器确认");
                return true;
            }
        } catch (SocketTimeoutException e) { // 超时未收到确认
            System.out.println("客户端：信号超时，重发...");
            return sendSignal(out, in, sendLog); // 递归重传（直到成功）
        }
        return false;
    }

    /**
     * 发送3个数据帧（核心方法）
     * 逻辑：对每个字符生成数据帧→发送→根据响应决定是否重传→直到收到对应ack
     */
    private static boolean sendDataFrames(DataOutputStream out, DataInputStream in, String data) throws IOException {
        // 循环发送3个帧（i=1→2→3，对应场景1→2→3）
        for (int i = 1; i <= FRAME_COUNT; i++) {
            char c = data.charAt(i); // 取第i个字符（索引1开始，避开开头的"$"）
            boolean isSent = false; // 标记当前帧是否发送成功

            // 重传循环：当前帧未成功则一直重传
            while (!isSent) {
                String frame = generateFrame(c); // 生成数据帧（二进制+CRC校验码）
                System.out.println("客户端：发送字符：'" + c + "', 数据帧：" + frame);
                out.writeUTF(frame); // 发送帧

                // 等待服务器响应，判断是否需要重传
                String response = waitForAcknowledgment(in, i);
                if (response.equals("ack" + i)) { // 收到对应序号的确认帧
                    isSent = true; // 标记为成功，退出重传循环
                    System.out.println("客户端：帧发送成功，进入下一个\n");
                }
            }
        }
        return true;
    }

    /**
     * 等待服务器响应（核心：重传机制的判断依据）
     * 逻辑：收到ack→成功；收到nak→重传；超时→重传（帧丢失）
     */
    private static String waitForAcknowledgment(DataInputStream in, int frameIndex) throws IOException {
        try {
            String response = in.readUTF(); // 读取服务器响应
            switch (response) {
                case "ack1", "ack2", "ack3" -> {
                    System.out.println("客户端：收到确认帧：" + response);
                    return response;
                }
                case "nak" -> {
                    System.out.println("客户端：收到否定帧：nak，准备重传");
                    return "nak";
                }
                default -> {
                    System.out.println("客户端：未知响应，准备重传");
                    return "unknown";
                }
            }
        } catch (SocketTimeoutException e) {
            System.out.println("客户端：超时未收到响应（帧可能丢失），准备重传");
            return "timeout"; // 触发重传
        }
    }

    /**
     * 生成数据帧（核心方法）
     * 作用：将字符转换为"8位二进制+CRC校验码"格式，用于传输
     */
    private static String generateFrame(char c) {
        int ascii = c; // 字符的ASCII码值（如'a'→97）
        String binary = String.format("%8s", Integer.toBinaryString(ascii)).replace(' ', '0');
        String crcCode = CRC.crcCheck(binary, "1011", null);
        return binary + "," + crcCode; // 帧格式：二进制,校验码（如01100001,101）
    }
}